#include <graphics/DynamicGeometry.h>

namespace ne
{
    DynamicGeometry::DynamicGeometry(const PrimitiveType pt)
        : mPrimitiveType(pt)
    {
    }

    PrimitiveType DynamicGeometry::getPrimitiveType() const
    {
        return mPrimitiveType;
    }

    void DynamicGeometry::addPosition(const Vector3 &v)
    {
        mPositionList.push_back(v);
    }

    void DynamicGeometry::addNormal(const Vector3 &n)
    {
        mNormalList.push_back(n);
    }

    void DynamicGeometry::addTangent(const Vector3 &t)
    {
        mTangentList.push_back(t);
    }

    void DynamicGeometry::addColor(const Color &c)
    {
        mColorList.push_back(c);
    }

    void DynamicGeometry::addTexCoord(const Vector2 &t)
    {
        mTexCoordList.push_back(t);
    }

    void DynamicGeometry::addIndex(const uint32_t index)
    {
        mIndexList.push_back(index);
    }

    uint32_t DynamicGeometry::getVertexCount() const
    {
        return uint32_t(mPositionList.size());
    }

    void DynamicGeometry::beginGeometry()
    {
        clearGeometry();
        _destroyBuffer();
    }

    void DynamicGeometry::endGeometry()
    {
        _createBuffer();
        _writeBuffer();
    }

    void DynamicGeometry::clearGeometry()
    {
        mPositionList.clear();
        mNormalList.clear();
        mTangentList.clear();
        mColorList.clear();
        mTexCoordList.clear();

        clearIndex();
    }

    void DynamicGeometry::beginIndex()
    {
        clearIndex();
        _destroyIndexBuffer();
    }

    void DynamicGeometry::endIndex()
    {
        _createIndexBuffer();
        _writeIndexBuffer();
    }

    void DynamicGeometry::clearIndex()
    {
        mIndexList.clear();
    }
    
    GLBufferObjectPtr DynamicGeometry::getVertexBuffer() const
    {
        return mpVertexBuffer;
    }

    GLBufferObjectPtr DynamicGeometry::getIndexBuffer() const
    {
        return mpIndexBuffer;
    }

    VertexDataPtr DynamicGeometry::getVertexData() const
    {
        return mpVertexData;
    }

    IndexDataPtr DynamicGeometry::getIndexData() const
    {
        return mpIndexData;
    }

    DynamicGeometry::~DynamicGeometry()
    {
        clearGeometry();
        _destroyBuffer();
    }

    void DynamicGeometry::_createBuffer()
    {
        if (mpVertexBuffer.isNull())
        {
            uint32_t vertexCount = uint32_t(mPositionList.size());
            if (vertexCount > 0)
            {
                bool useNormal = (mNormalList.size() >= vertexCount);
                bool useTangent = (mTangentList.size() >= vertexCount);
                bool useColor = (mColorList.size() >= vertexCount);
                bool useTexCoord = (mTexCoordList.size() >= vertexCount);

                uint32_t stride = VertexAttribute::GetAttributeSize(VAT_FLOAT3);
                if (useNormal)
                {
                    stride += VertexAttribute::GetAttributeSize(VAT_FLOAT3);
                }
                if (useTangent)
                {
                    stride += VertexAttribute::GetAttributeSize(VAT_FLOAT3);
                }
                if (useColor)
                {
                    stride += VertexAttribute::GetAttributeSize(VAT_FLOAT4);
                }
                if (useTexCoord)
                {
                    stride += VertexAttribute::GetAttributeSize(VAT_FLOAT2);
                }

                uint32_t vertexBufferSize = stride * vertexCount;
                mpVertexBuffer = GLBufferObjectManager::GetSingleton().createBufferObject(
                    BOT_ARRAY_BUFFER, BOU_STATIC_DRAW, vertexBufferSize);
                mpVertexData.bind(new VertexData(mPrimitiveType));

                uint32_t bufferSource = mpVertexBuffer->getSource();
                uint32_t bufferOffset = 0;
                mpVertexData->createAttribute(bufferSource, VAS_POSITION, VAT_FLOAT3, vertexCount, stride, bufferOffset);
                bufferOffset += VertexAttribute::GetAttributeSize(VAT_FLOAT3);
                if (useNormal)
                {
                    mpVertexData->createAttribute(bufferSource, VAS_NORMAL, VAT_FLOAT3, vertexCount, stride, bufferOffset);
                    bufferOffset += VertexAttribute::GetAttributeSize(VAT_FLOAT3);
                }
                if (useTangent)
                {
                    mpVertexData->createAttribute(bufferSource, VAS_TANGENT, VAT_FLOAT3, vertexCount, stride, bufferOffset);
                    bufferOffset += VertexAttribute::GetAttributeSize(VAT_FLOAT3);
                }
                if (useColor)
                {
                    mpVertexData->createAttribute(bufferSource, VAS_COLOR, VAT_FLOAT4, vertexCount, stride, bufferOffset);
                    bufferOffset += VertexAttribute::GetAttributeSize(VAT_FLOAT4);
                }
                if (useTexCoord)
                {
                    mpVertexData->createAttribute(bufferSource, VAS_TEXTURE_COORD, VAT_FLOAT2, vertexCount, stride, bufferOffset);
                    bufferOffset += VertexAttribute::GetAttributeSize(VAT_FLOAT2);
                }
                mpVertexData->setStride(stride);

                _createIndexBuffer();
            }
        }
    }

    void DynamicGeometry::_writeBuffer()
    {
        uint32_t vertexCount = uint32_t(mPositionList.size());
        if (vertexCount > 0 && mpVertexBuffer.notNull())
        {
            real *pVertexBufferIterator = static_cast<real*>(mpVertexBuffer->lock(BOA_WRITE_ONLY, 0, mpVertexData->getSize(), false));

            bool useNormal = (mNormalList.size() >= vertexCount);
            bool useTangent = (mTangentList.size() >= vertexCount);
            bool useColor = (mColorList.size() >= vertexCount);
            bool useTexCoord = (mTexCoordList.size() >= vertexCount);

            std::list<Vector3>::const_iterator itNormal = mNormalList.begin();
            std::list<Vector3>::const_iterator itTangent = mTangentList.begin();
            std::list<Color>::const_iterator itColor = mColorList.begin();
            std::list<Vector2>::const_iterator itTexCoord = mTexCoordList.begin();

            for (std::list<Vector3>::const_iterator it=mPositionList.begin(); it!=mPositionList.end(); ++it)
            {
                *pVertexBufferIterator++ = it->x;
                *pVertexBufferIterator++ = it->y;
                *pVertexBufferIterator++ = it->z;
                if (useNormal)
                {
                    *pVertexBufferIterator++ = itNormal->x;
                    *pVertexBufferIterator++ = itNormal->y;
                    *pVertexBufferIterator++ = itNormal->z;
                    ++itNormal;
                }
                if (useTangent)
                {
                    *pVertexBufferIterator++ = itTangent->x;
                    *pVertexBufferIterator++ = itTangent->y;
                    *pVertexBufferIterator++ = itTangent->z;
                    ++itTangent;
                }
                if (useColor)
                {
                    *pVertexBufferIterator++ = itColor->r;
                    *pVertexBufferIterator++ = itColor->g;
                    *pVertexBufferIterator++ = itColor->b;
                    *pVertexBufferIterator++ = itColor->a;
                    ++itColor;
                }
                if (useTexCoord)
                {
                    *pVertexBufferIterator++ = itTexCoord->x;
                    *pVertexBufferIterator++ = itTexCoord->y;
                    ++itTexCoord;
                }
            }
            mpVertexBuffer->unlock();

            _writeIndexBuffer();
        }
    }

    void DynamicGeometry::_destroyBuffer()
    {
        if (mpVertexBuffer.notNull())
        {
            GLBufferObjectManager::GetSingleton().removeBySource(mpVertexBuffer->getSource());
            mpVertexBuffer.setNull();
        }
        mpVertexData.setNull();

        _destroyIndexBuffer();
    }

    void DynamicGeometry::_createIndexBuffer()
    {
        if (!mIndexList.empty() && mpIndexBuffer.isNull())
        {
            uint32_t indexCount = uint32_t(mIndexList.size());
            AttributeType attributeType = VertexAttribute::GetIndexAttributeType(indexCount);
            uint32_t indexBufferSize = VertexAttribute::GetAttributeSize(attributeType) * indexCount;
            mpIndexBuffer = GLBufferObjectManager::GetSingleton().createBufferObject(
                BOT_ELEMENT_ARRAY_BUFFER, BOU_STATIC_DRAW, indexBufferSize);
            mpIndexData.bind(new IndexData(mPrimitiveType, mpIndexBuffer->getSource(),
                             attributeType, indexCount, 0));
        }
    }

    void DynamicGeometry::_writeIndexBuffer()
    {
        if (mpIndexData->getSize() > 0 && mpIndexBuffer.notNull())
        {
            uint32_t indexCount = uint32_t(mIndexList.size());
            AttributeType attributeType = VertexAttribute::GetIndexAttributeType(indexCount);
            switch (attributeType)
            {
                case VAT_BYTE:
                {
                    byte *pIndexBufferIterator = static_cast<byte*>(mpIndexBuffer->lock(BOA_WRITE_ONLY, 0, mpIndexData->getSize(), false));
                    for (std::list<uint32_t>::const_iterator it=mIndexList.begin(); it!=mIndexList.end(); ++it)
                    {
                        *pIndexBufferIterator++ = byte(*it);
                    }
                    break;
                }

                case VAT_UNSIGNED_SHORT:
                {
                    uint16_t *pIndexBufferIterator = static_cast<uint16_t*>(mpIndexBuffer->lock(BOA_WRITE_ONLY, 0, mpIndexData->getSize(), false));
                    for (std::list<uint32_t>::const_iterator it=mIndexList.begin(); it!=mIndexList.end(); ++it)
                    {
                        *pIndexBufferIterator++ = uint16_t(*it);
                    }
                    break;
                }
                
                default:
                {
                    uint32_t *pIndexBufferIterator = static_cast<uint32_t*>(mpIndexBuffer->lock(BOA_WRITE_ONLY, 0, mpIndexData->getSize(), false));
                    for (std::list<uint32_t>::const_iterator it=mIndexList.begin(); it!=mIndexList.end(); ++it)
                    {
                        *pIndexBufferIterator++ = (*it);
                    }
                    break;
                }
            }
            mpIndexBuffer->unlock();
        }
    }
    void DynamicGeometry::_destroyIndexBuffer()
    {
        if (mpIndexBuffer.notNull())
        {
            GLBufferObjectManager::GetSingleton().removeBySource(mpIndexBuffer->getSource());
            mpIndexBuffer.setNull();
        }
        mpIndexData.setNull();
    }
}
